package handler

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"ruoyi-go-echo-ddd/internal/infrastructure/http/request"
	"ruoyi-go-echo-ddd/internal/infrastructure/http/response"
	"ruoyi-go-echo-ddd/internal/infrastructure/utils"
	"sort"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/labstack/echo/v4"
	"github.com/rs/zerolog"
)

const (
	chunkDirPrefix  = "upload/chunk/"
	finalDirPath    = "upload/final/"
	chunkFilePrefix = "chunk_"
	chunkFileSuffix = ".tmp"
	maxOpenFiles    = 100 // 防止文件描述符耗尽
)

type UploadHandler struct {
}

func NewUploadHandler() *UploadHandler {
	return &UploadHandler{}
}

// ChunkUploadInit 分片上传初始化处理
func (h *UploadHandler) ChunkUploadInit(c echo.Context) error {
	logger := zerolog.DefaultContextLogger
	var param = new(request.ChunkUploadInitParam)
	if err := BindParam(c, param); err != nil {
		logger.Error().Err(err).Msg("分片上传初始化错误")
		response.NewRespCodeErr(c, 500, err)
		return err
	}

	logger.Info().Msgf("分片上传初始化入参==\n%s", utils.ToJsonFormat(param))

	response.NewRespCodeData(c, 200, &response.ChunkUploadResp{
		UploadId:  utils.NormalUUID(),
		UploadMD5: param.FileMD5,
		FileSize:  param.FileSize,
	})
	return nil
}

// ChunkUpload 分片上传处理(执行上传操作)
func (h *UploadHandler) ChunkUpload(c echo.Context) error {
	logger := zerolog.DefaultContextLogger
	var param = new(request.ChunkUploadParam)
	if err := BindParam(c, param); err != nil {
		logger.Error().Err(err).Msg("分片上传处理错误")
		response.NewRespCodeErr(c, 500, err)
		return err
	}

	logger.Info().Msgf("分片上传入参==\n%s", utils.ToJsonFormat(param))

	if param.File == nil {
		response.NewRespCodeMsg(c, 500, "分片文件不能为空")
		return errors.New("分片文件不能为空")
	}

	file, err := param.File.Open()
	if err != nil {
		logger.Error().Err(err).Msg("打开上传文件失败")
		response.NewRespCodeMsg(c, 500, "打开上传文件失败")
		return err
	}

	defer file.Close()

	// 安全处理输入，防止路径遍历
	cleanUploadID := filepath.Base(param.UploadID)
	cleanFileMD5 := filepath.Base(param.FileMD5)

	chunkDir := filepath.Join(chunkDirPrefix, cleanFileMD5+"_"+cleanUploadID)
	// 创建分片文件路径
	if err := os.MkdirAll(chunkDir, os.ModePerm); err != nil {
		logger.Error().Err(err).Msg("创建分片目录失败")
		response.NewRespCodeMsg(c, 500, "创建分片目录失败")
		return err
	}

	chunkTmpPath := filepath.Join(chunkDir, chunkFilePrefix+param.Index+chunkFileSuffix)

	// 获取文件锁防止并发写入
	lockFile := chunkTmpPath + ".lock"
	lock, err := os.Create(lockFile)
	if err != nil {
		response.NewRespCodeMsg(c, 500, "创建文件锁失败")
		return err
	}

	lockPath, _ := filepath.Abs(lockFile)

	logger.Info().Str("锁名称", lockFile).Str("锁路径", lockPath).Msg("分片上传操作文件锁创建成功")

	//defer os.Remove(lockFile)
	//defer lock.Close()

	if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil {
		response.NewRespCodeMsg(c, 500, "分片上传获取锁失败")
		return err
	}
	//defer syscall.Flock(int(lock.Fd()), syscall.LOCK_UN)

	defer func(lockFile string) {
		// 先解锁
		syscall.Flock(int(lock.Fd()), syscall.LOCK_UN)
		// 关闭文件
		lock.Close()
		// 删除锁
		if err = os.Remove(lockFile); err != nil {
			logger.Error().Err(err).Str("lockFile", lockFile).Msg("删除分片上传文件锁失败")
		} else {
			logger.Info().Str("lockFile", lockFile).Msg("删除分片上传文件锁成功")
		}
	}(lockFile)

	// 检查分片是否已存在
	if _, err := os.Stat(chunkTmpPath); err == nil {
		logger.Info().Str("path", chunkTmpPath).Msg("分片已存在，跳过上传")
		response.NewRespCodeMsg(c, 200, "分片已存在")
		return nil
	}

	osFile, err := os.Create(chunkTmpPath)
	if err != nil {
		logger.Error().Err(err).Msg("创建分片上传文件锁失败")
		response.NewRespCodeErr(c, 500, err)
		return err
	}

	defer osFile.Close()

	if _, err := io.Copy(osFile, file); err != nil {
		logger.Error().Err(err).Msg("写入分片失败")
		response.NewRespCodeErr(c, 500, err)
		return err
	}

	response.NewRespCodeMsg(c, 200, "分片文件上传成功")
	return nil
}

// ChunkUploadMerge 分片上传合并处理
func (h *UploadHandler) ChunkUploadMerge(c echo.Context) error {
	logger := zerolog.DefaultContextLogger
	var param = new(request.ChunkUploadMerge)
	if err := BindParam(c, param); err != nil {
		logger.Error().Err(err).Msg("分片上传合并错误")
		response.NewRespCodeErr(c, 500, err)
		return err
	}

	logger.Info().Msgf("分片合并入参\n%s", utils.ToJsonFormat(param))

	// 安全处理输入，防止路径遍历
	cleanUploadID := filepath.Base(param.UploadID)
	cleanFileMD5 := filepath.Base(param.FileMD5)
	cleanFileName := filepath.Base(param.FileName)

	// 判断目录是否存在，然后遍历该目录下的文件
	chunkPath := filepath.Join(chunkDirPrefix, cleanFileMD5+"_"+cleanUploadID)
	files, err := os.ReadDir(chunkPath)
	if err != nil {
		if os.IsNotExist(err) {
			response.NewRespCodeMsg(c, 500, "分片目录不存在")
			return err
		}
		logger.Error().Err(err).Msg("分片合并读取分片目录失败")
		response.NewRespCodeMsg(c, 500, "读取分片目录失败")
		return err
	}

	// 按照 chunk_索引.tmp 排序，这里的排序存在bug，chunk_10.tmp排在chunk_2.tmp，导致合并后的zip解压失败
	// sort.Slice(files, func(i, j int) bool {
	// 	return files[i].Name() < files[j].Name()
	// })

	logger.Info().Msg("排序前文件列表:")
	for _, file := range files {
		logger.Info().Msgf("%s", file.Name())
	}

	sort.Slice(files, func(i, j int) bool {
		/* getIndex := func(name string) int {
			// 去掉 "chunk_" 和 ".tmp"
			idxStr := strings.TrimSuffix(strings.TrimPrefix(name, "chunk_"), ".tmp")
			idx, _ := strconv.Atoi(idxStr)
			return idx
		}
		return getIndex(files[i].Name()) < getIndex(files[j].Name()) */
		return getChunkIndex(files[i].Name()) < getChunkIndex(files[j].Name())
	})

	// 排序后打印最终文件列表
	logger.Info().Msg("排序后文件列表:")
	for _, file := range files {
		logger.Info().Msgf("%s", file.Name())
	}

	finalDir := filepath.Join(finalDirPath)
	if err := os.MkdirAll(finalDir, os.ModePerm); err != nil {
		logger.Error().Err(err).Msg("分片合并创建目标目录失败")
		response.NewRespCodeErr(c, 500, err)
		return err
	}

	// 构建初始合并路径
	mergeFilePath := filepath.Join(finalDir, cleanFileName)
	// 如果文件已存在，则加上时间戳
	if _, err := os.Stat(mergeFilePath); err == nil {
		ext := filepath.Ext(cleanFileName)
		nameOnly := strings.TrimSuffix(cleanFileName, ext)
		timestamp := utils.TimeNowFormat("20060102150405") // 例如 20250701131800
		mergeFilePath = filepath.Join(finalDir, fmt.Sprintf("%s_%s%s", nameOnly, timestamp, ext))
	}

	// 使用文件锁保护合并过程
	mergeLock := mergeFilePath + ".lock"
	lock, err := os.Create(mergeLock)
	if err != nil {
		response.NewRespCodeMsg(c, 500, "创建合并锁失败")
		return err
	}

	logger.Info().Str("锁名称", mergeLock).Msg("分片合并操作文件锁")
	defer os.Remove(mergeLock)
	defer lock.Close()

	if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil {
		response.NewRespCodeMsg(c, 500, "分片合并获取锁失败")
		return err
	}
	defer syscall.Flock(int(lock.Fd()), syscall.LOCK_UN)

	// 创建目标文件
	dst, err := os.Create(mergeFilePath)
	if err != nil {
		logger.Error().Err(err).Msg("分片合并创建文件失败")
		response.NewRespCodeErr(c, 500, err)
		return err
	}
	defer dst.Close()

	// 逐个写入分片
	/* for _, file := range files {
		partPath := filepath.Join(chunkPath, file.Name())
		src, err := os.Open(partPath)
		if err != nil {
			logger.Error().Err(err).Str("file", partPath).Msg("分片合并打开分片文件失败")
			response.NewRespCodeErr(c, 500, err)
			return err
		}
		if _, err := io.Copy(dst, src); err != nil {
			src.Close()
			logger.Error().Err(err).Str("file", partPath).Msg("分片合并写入合并文件失败")
			response.NewRespCodeErr(c, 500, err)
			return err
		}
		src.Close()
	} */

	bufWriter := bufio.NewWriterSize(dst, 2*1024*1024) // 2MB缓冲
	defer bufWriter.Flush()

	// 分批处理防止文件描述符耗尽
	for start := 0; start < len(files); start += maxOpenFiles {
		end := min(start+maxOpenFiles, len(files))

		batch := files[start:end]
		sources := make([]*os.File, 0, len(batch))

		// 打开当前批次文件
		for _, file := range batch {
			partPath := filepath.Join(chunkPath, file.Name())
			src, err := os.Open(partPath)
			if err != nil {
				// 关闭已打开的文件
				for _, f := range sources {
					f.Close()
				}
				response.NewRespCodeMsg(c, 500, "打开分片文件失败")
				return err
			}
			sources = append(sources, src)
		}

		// 复制当前批次
		for _, src := range sources {
			if _, err := io.Copy(bufWriter, src); err != nil {
				// 关闭所有文件
				for _, f := range sources {
					f.Close()
				}
				response.NewRespCodeMsg(c, 500, "写入合并文件失败")
				return err
			}
			src.Close()
		}
	}

	if err := CheckMergeFileMD5(c, mergeFilePath, cleanFileMD5); err != nil {
		response.NewRespCodeErr(c, 500, err)
		return err
	}

	// 异步删除分片临时目录
	go func() {
		if err := os.RemoveAll(chunkPath); err != nil {
			logger.Warn().Err(err).Msg("删除临时分片目录失败（可忽略）")
		}
		logger.Info().Str("目录", chunkPath).Msg("分片上传临时目录删除成功")
	}()

	absPath, _ := filepath.Abs(dst.Name())
	logger.Info().Str("路径", absPath).Msg("分片文件合并成功")
	response.NewRespCodeMsg(c, 200, "分片文件合并成功")
	return nil
}

// getChunkIndex 从文件名提取分片索引
func getChunkIndex(name string) int {
	idxStr := strings.TrimPrefix(name, chunkFilePrefix)
	idxStr = strings.TrimSuffix(idxStr, chunkFileSuffix)
	idx, _ := strconv.Atoi(idxStr)
	return idx
}

// CheckMergeFileMD5 校验合并后文件的md5
func CheckMergeFileMD5(c echo.Context, mergeFilePath, cleanFileMD5 string) error {
	logger := zerolog.DefaultContextLogger
	// 添加耗时统计
	start := time.Now()
	// md5校验
	calcFileMD5, err := utils.CalculateFileMD5(mergeFilePath)
	if err != nil {
		logger.Error().Err(err).Msg("计算合并后文件md5失败")
		return errors.New("md5校验失败")
	}

	if calcFileMD5 != cleanFileMD5 {
		logger.Error().Str("expected", cleanFileMD5).Str("actual", calcFileMD5).Msg("合并后文件MD5不一致")
		return errors.New("参数md5与校验md5不一致")
	}

	// 计算耗时
	elapsed := time.Since(start)

	logger.Info().Str("参数md5", cleanFileMD5).Str("校验md5", calcFileMD5).Dur("耗时(毫秒)", elapsed).Msg("文件合并md5校验成功")
	return nil
}
